package cn.hackcloud.concurrency;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.IntStream;

public class BooleanLock implements Lock {

    public static void main(String[] args) {
        final BooleanLock lock = new BooleanLock();
        IntStream.range(0, 10).mapToObj(i -> new Thread(lock::syncMethod)).forEach(Thread::start);
    }

    public void syncMethod() {
        try {
            this.lock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            int random = ThreadLocalRandom.current().nextInt(10);
            System.out.println(Thread.currentThread().getName() + " get block");
            TimeUnit.SECONDS.sleep(random);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            this.unlock();
        }
    }

    private Thread currentThread;
    private boolean locked = false;
    private List<Thread> blockedList = new ArrayList<>();

    @Override
    public void lock() throws InterruptedException {
        synchronized (this) {
            while (locked) {
                if (!this.blockedList.contains(Thread.currentThread())) {
                    blockedList.add(Thread.currentThread());
                }
                this.wait();
            }
            blockedList.remove(Thread.currentThread());
            this.locked = true;
            this.currentThread = Thread.currentThread();
        }
    }

    @Override
    public void lock(long mills) throws InterruptedException, TimeoutException {
        synchronized (this) {
            if (mills <= 0) {
                this.lock();
            } else {
                long remainingMills = mills;
                long endMills = System.currentTimeMillis() + remainingMills;
                while (locked) {
                    if (remainingMills <= 0)
                        throw new TimeoutException("cant't get the lock during " + mills);
                    if (!blockedList.contains(Thread.currentThread())) {
                        this.blockedList.add(Thread.currentThread());
                    }
                    this.wait(remainingMills);
                    remainingMills = endMills - System.currentTimeMillis();
                }
                blockedList.remove(Thread.currentThread());
                this.locked = true;
                this.currentThread = Thread.currentThread();
            }
        }
    }

    @Override
    public void unlock() {
        synchronized (this) {
            if (currentThread == Thread.currentThread()) {
                this.locked = false;
                Optional.of(Thread.currentThread().getName() + " release the lock.").ifPresent(System.out::println);
                this.notifyAll();
            }
        }
    }

    @Override
    public List<Thread> getBlockedThreads() {
        return Collections.unmodifiableList(blockedList);
    }


}
